﻿using static Forza_Mods_AIO.Resources.Memory;

namespace Forza_Mods_AIO.Cheats.ForzaHorizon4;

public static class CarCheatsOffsets
{
    public const int VelEnabled = 0x1C9 + 1;
    public const int VelBoost = 0x1CA + 1;
    public const int VelLimit = 0x1CE + 1;
    public const int BrakeHackEnabled = 0x1D2 + 1;
    public const int BrakeHackBoost = 0x1D3 + 1;
    public const int StopAllWheelsEnabled = 0x1D7 + 1;
    public const int JumpHackEnabled = 0x1D8 + 1;
    public const int JumpHackBoost = 0x1D9 + 1;
    public const int WheelspeedEnabled = 0x1DD + 1;
    public const int WheelspeedMode = 0x1DE + 1;
    public const int WheelspeedBoost = 0x1DF + 1;
    public const int WheelspeedLimit = 0x1E3 + 1;
    public const int LocalPlayer = 0x1E7 + 1;
}

public class CarCheats : CheatsUtilities, ICheatsBase
{
    private UIntPtr _localPlayerHookAddress;
    public UIntPtr LocalPlayerHookDetourAddress;
    private UIntPtr _accelAddress;
    public UIntPtr AccelDetourAddress;
    private UIntPtr _gravityAddress;
    public UIntPtr GravityDetourAddress;
    
    public async Task CheatLocalPlayer()
    {
        _localPlayerHookAddress = 0;
        LocalPlayerHookDetourAddress = 0;

        const string sig = "F3 44 ? ? ? ? 41 0F ? ? 44 0F ? ? ? ? F3 0F ? ? ? ? ? ? 44 0F";
        _localPlayerHookAddress = await SmartAobScan(sig);
        if (_localPlayerHookAddress > 0)
        {
            var asm = new byte[]
            {
                0x50, 0x53, 0x48, 0x83, 0xEC, 0x30, 0xF3, 0x0F, 0x7F, 0x04, 0x24, 0xF3, 0x0F, 0x7F, 0x4C, 0x24, 0x10,
                0xF3, 0x0F, 0x7F, 0x54, 0x24, 0x20, 0xF3, 0x0F, 0x10, 0x49, 0x20, 0xF3, 0x0F, 0x10, 0x51, 0x28, 0xF3,
                0x0F, 0x59, 0xC9, 0xF3, 0x0F, 0x59, 0xD2, 0xF3, 0x0F, 0x58, 0xCA, 0xF3, 0x0F, 0x51, 0xC1, 0xF3, 0x0F,
                0x10, 0xC8, 0x68, 0x29, 0x5C, 0x0F, 0x40, 0xF3, 0x0F, 0x59, 0x0C, 0x24, 0x48, 0x83, 0xC4, 0x08, 0x80,
                0x3D, 0x80, 0x01, 0x00, 0x00, 0x01, 0x75, 0x34, 0x0F, 0x2F, 0x0D, 0x7C, 0x01, 0x00, 0x00, 0x77, 0x2B,
                0xF3, 0x0F, 0x10, 0x41, 0x20, 0xF3, 0x0F, 0x10, 0x51, 0x28, 0xF3, 0x0F, 0x59, 0x05, 0x64, 0x01, 0x00,
                0x00, 0xF3, 0x0F, 0x59, 0x15, 0x5C, 0x01, 0x00, 0x00, 0xF3, 0x0F, 0x11, 0x41, 0x20, 0xF3, 0x0F, 0x11,
                0x51, 0x28, 0xC6, 0x05, 0x4A, 0x01, 0x00, 0x00, 0x00, 0x80, 0x3D, 0x4C, 0x01, 0x00, 0x00, 0x01, 0x75,
                0x2B, 0xF3, 0x0F, 0x10, 0x41, 0x20, 0xF3, 0x0F, 0x10, 0x51, 0x28, 0xF3, 0x0F, 0x59, 0x05, 0x39, 0x01,
                0x00, 0x00, 0xF3, 0x0F, 0x59, 0x15, 0x31, 0x01, 0x00, 0x00, 0xF3, 0x0F, 0x11, 0x41, 0x20, 0xF3, 0x0F,
                0x11, 0x51, 0x28, 0xC6, 0x05, 0x1F, 0x01, 0x00, 0x00, 0x00, 0x80, 0x3D, 0x1E, 0x01, 0x00, 0x00, 0x01,
                0x75, 0x19, 0xF3, 0x0F, 0x10, 0x41, 0x24, 0xF3, 0x0F, 0x58, 0x05, 0x10, 0x01, 0x00, 0x00, 0xF3, 0x0F,
                0x11, 0x41, 0x24, 0xC6, 0x05, 0x03, 0x01, 0x00, 0x00, 0x00, 0x80, 0x3D, 0xFB, 0x00, 0x00, 0x00, 0x01,
                0x75, 0x28, 0x48, 0x31, 0xC0, 0x48, 0x31, 0xDB, 0x48, 0x69, 0xC3, 0x90, 0x08, 0x00, 0x00, 0xC7, 0x84,
                0x08, 0x14, 0x1E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0xFF, 0xC3, 0x48, 0x83, 0xFB, 0x03, 0x76,
                0xE5, 0xC6, 0x05, 0xD1, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3D, 0xD0, 0x00, 0x00, 0x00, 0x01, 0x0F, 0x85,
                0x8D, 0x00, 0x00, 0x00, 0x8B, 0x05, 0xCA, 0x00, 0x00, 0x00, 0x39, 0x81, 0xC0, 0x26, 0x00, 0x00, 0x0F,
                0x87, 0x7B, 0x00, 0x00, 0x00, 0xF3, 0x0F, 0x10, 0x05, 0xB2, 0x00, 0x00, 0x00, 0x68, 0x00, 0x00, 0x20,
                0x41, 0xF3, 0x0F, 0x5E, 0x04, 0x24, 0x48, 0x83, 0xC4, 0x08, 0xF3, 0x0F, 0x10, 0x89, 0x14, 0x1E, 0x00,
                0x00, 0x80, 0x3D, 0x94, 0x00, 0x00, 0x00, 0x00, 0x75, 0x06, 0xF3, 0x0F, 0x58, 0xC8, 0xEB, 0x28, 0x68,
                0x00, 0x00, 0xC8, 0x42, 0xF3, 0x0F, 0x5E, 0x0C, 0x24, 0x48, 0x83, 0xC4, 0x08, 0xF3, 0x0F, 0x58, 0xC8,
                0x68, 0xEC, 0x51, 0x80, 0x3F, 0xF3, 0x0F, 0x59, 0x0C, 0x24, 0x48, 0x83, 0xC4, 0x08, 0xF3, 0x0F, 0x58,
                0x89, 0x14, 0x1E, 0x00, 0x00, 0x48, 0x31, 0xC0, 0x48, 0x31, 0xDB, 0x48, 0x69, 0xC3, 0x90, 0x08, 0x00,
                0x00, 0xF3, 0x0F, 0x11, 0x8C, 0x08, 0x14, 0x1E, 0x00, 0x00, 0x48, 0xFF, 0xC3, 0x48, 0x83, 0xFB, 0x03,
                0x76, 0xE7, 0xC6, 0x05, 0x3D, 0x00, 0x00, 0x00, 0x00, 0xF3, 0x0F, 0x6F, 0x54, 0x24, 0x20, 0xF3, 0x0F,
                0x6F, 0x4C, 0x24, 0x10, 0xF3, 0x0F, 0x6F, 0x04, 0x24, 0x48, 0x83, 0xC4, 0x30, 0x5B, 0x58, 0xF3, 0x44,
                0x0F, 0x10, 0x49, 0x24, 0x48, 0x89, 0x0D, 0x23, 0x00, 0x00, 0x00
            };
            LocalPlayerHookDetourAddress = GetInstance().CreateDetour(_localPlayerHookAddress, asm, 6);
            return;
        }
        
        ShowError("Local Player Hook", sig);
    }

    public async Task CheatAccel()
    {
        _accelAddress = 0;
        AccelDetourAddress = 0;

        const string sig = "F3 0F ? ? ? 0F C6 C0 ? 0F 59 ? ? 0F C6 D2";
        _accelAddress = await SmartAobScan(sig);
        if (_accelAddress > 0)
        {
            if (LocalPlayerHookDetourAddress == 0)
            {
                await CheatLocalPlayer();
            }

            if (LocalPlayerHookDetourAddress == 0)
            {
                return;
            }
            
            var localPlayerAddr = BitConverter.GetBytes(LocalPlayerHookDetourAddress + CarCheatsOffsets.LocalPlayer);
            var asm = new byte[]
            {
                0xF3, 0x0F, 0x10, 0x46, 0x0C, 0x48, 0xBA, localPlayerAddr[0], localPlayerAddr[1], localPlayerAddr[2],
                localPlayerAddr[3], localPlayerAddr[4], localPlayerAddr[5], localPlayerAddr[6], localPlayerAddr[7],
                0x48, 0x39, 0x32, 0x75, 0x19, 0xF3, 0x0F, 0x11, 0x05, 0x1B, 0x00, 0x00, 0x00, 0x80, 0x3D, 0x0F, 0x00,
                0x00, 0x00, 0x01, 0x75, 0x08, 0xF3, 0x0F, 0x10, 0x05, 0x06, 0x00, 0x00, 0x00
            };
            AccelDetourAddress = GetInstance().CreateDetour(_accelAddress, asm, 5);
            return;
        }
        
        ShowError("Accel", sig);
    }
    
    public async Task CheatGravity()
    {
        _gravityAddress = 0;
        GravityDetourAddress = 0;

        const string sig = "F3 0F ? ? ? F3 41 ? ? ? 0F 10";
        _gravityAddress = await SmartAobScan(sig);
        if (_gravityAddress > 0)
        {
            if (LocalPlayerHookDetourAddress == 0)
            {
                await CheatLocalPlayer();
            }

            if (LocalPlayerHookDetourAddress == 0) return;
            
            var localPlayerAddr = BitConverter.GetBytes(LocalPlayerHookDetourAddress + CarCheatsOffsets.LocalPlayer);
            var asm = new byte[]
            {
                0xF3, 0x0F, 0x10, 0x7B, 0x08, 0x48, 0xBA, localPlayerAddr[0], localPlayerAddr[1], localPlayerAddr[2],
                localPlayerAddr[3], localPlayerAddr[4], localPlayerAddr[5], localPlayerAddr[6], localPlayerAddr[7],
                0x48, 0x39, 0x1A, 0x75, 0x19, 0xF3, 0x0F, 0x11, 0x3D, 0x1B, 0x00, 0x00, 0x00, 0x80, 0x3D, 0x0F, 0x00,
                0x00, 0x00, 0x01, 0x75, 0x08, 0xF3, 0x0F, 0x10, 0x3D, 0x06, 0x00, 0x00, 0x00
            };
            GravityDetourAddress = GetInstance().CreateDetour(_gravityAddress, asm, 5);
            return;
        }
        
        ShowError("Gravity", sig);
    }
    
    public void Cleanup()
    {
        var mem = GetInstance();
        
        if (LocalPlayerHookDetourAddress > 0)
        {
            mem.WriteArrayMemory(_localPlayerHookAddress, new byte[] { 0xF3, 0x44, 0x0F, 0x10, 0x49, 0x24 });
            Free(LocalPlayerHookDetourAddress);
        }

        if (AccelDetourAddress > 0)
        {
            mem.WriteArrayMemory(_gravityAddress, new byte[] { 0xF3, 0x0F, 0x10, 0x46, 0x0C });
            Free(AccelDetourAddress);
        }

        if (GravityDetourAddress > 0)
        {
            mem.WriteArrayMemory(_gravityAddress, new byte[] { 0xF3, 0x0F, 0x10, 0x7B, 0x08 });
            Free(GravityDetourAddress);
        }
    }

    public void Reset()
    {
        var fields = typeof(CarCheats).GetFields().Where(f => f.FieldType == typeof(UIntPtr));
        foreach (var field in fields)
        {
            field.SetValue(this, UIntPtr.Zero);
        }
    }
}